/* 
 * Title: "jListFetcher" - A jQuery Plugin
 * Version: 0.1
 * By: Airshow (http://www.daniweb.com/members/Airshow/512379)
 * Description: A jQuery plugin for fetching a tabular list from a server script,
 *   against a set of criteria entered/selected by the user.
 * Plugin Design Pattern: http://docs.jquery.com/Plugins/Authoring#Plugin_Methods
 * Depends on: jQuery. Developed with v1.4.4 . (May work with earlier versions)
 * License: MIT (http://www.jclark.com/xml/copying.txt)
 * Contributions: By PayPal to voussy_voussy@ntlworld.com
 */
(function($){
	// **********************************
	// ***** Start: Private Members *****
	function constrainList($rows){
		//"this" is a tbody object on which this plugin has been invoked.
		var settings = this.data('jListFetcher').settings;
		var $menus = this.data('jListFetcher').$menus;
		//Show all current rows
		$rows.show();
		//Then, selectively hide rows if they don't match any of the current menu selections.
		var $m, name, $row;
		$rows.each(function(i){
			$row = $(this);
			$menus.each(function(j){
				$m = $(this);
				name = $m.attr('name');
				if( $m.val()!==settings.wildcard && $m.val()!==$row.find('td.'+name).html() ){
					$row.hide();//row doesn't match current selection, so hide it.
					return false;//no need to look further in this row; break out of inner loop.
				}
			});
		});
		return setRegime.call(this, 'normal');
	}
	
	function setRegime(r){
		//"this" is a tbody object on which this plugin has been invoked.
		var settings = this.data('jListFetcher').settings;
		r = !r ? '' : r;
		if(settings.$emptyList) { settings.$emptyList.hide(); }
		if(settings.$searching) { settings.$searching.hide(); }
		if(settings.$error) { settings.$error.hide(); }
		this.hide();
		switch(r){
			case 'normal':
				var rowsShowing = this.show().find('tr').not(':hidden').length;
				if(rowsShowing>0) {
					return rowsShowing;
				}
				else{
					this.hide();
					if(settings.$emptyList) { settings.$emptyList.show(); }
				}
			break;
			case 'searching':
				if(settings.$searching) { settings.$searching.show(); }
			break;
			case 'error':
				if(settings.$error) { settings.$error.show(); }
			break;
		}
		return 0;
	}

	function setList(e){
		//"this" is a tbody object on which this plugin has been invoked.
		var settings     = this.data('jListFetcher').settings;
		var ajaxSettings = this.data('jListFetcher').ajaxSettings;
		var $menus       = this.data('jListFetcher').$menus;
		//Scan menus to test whether current selections are compatible with last served list.
		var $m, lastServed, match = true;
		$menus.each(function(i){
			$m = $(this);
			lastServed = $m.data('jListFetcher').lastServed;

			match = match && (typeof lastServed !== 'undefined') && ( lastServed===settings.wildcard || lastServed===$m.val() );
		});
		var $rows = this.find('tr');
		if(!e.forceAjax && match && ($rows.length > 0) && ($rows.length < settings.maxResults)){
			//manipulate last served list to show/hide items as apprioriate
			constrainList.call(this, $rows);
		}
		else{
			//go to server (ajax) for new list
			setRegime.call(this, 'searching');
			ajaxSettings.data = settings.$searchForm.serialize();
			$.ajax(ajaxSettings);
		}
	}
	// ***** Fin: Private Members *****
	// ********************************

	// *********************************
	// ***** Start: Public Methods *****
	var methods = {
		init : function(options) {
			//"this" is a jquery object on which this plugin has been invoked.
			return this.each(function(index){
				//"$this" is a tbody object on which this plugin has been invoked.
				var $this = $(this);
				var data = $this.data('jListFetcher');

				// If the plugin hasn't been initialized yet
				if (!data){
					var settings = {
						$searchForm : null,
						$searching : null,
						$emptyList : null,
						$error : null,
						wildcard : '*',//Make sure each menu has an "Any" option with value="*"
						maxResults : 100,//the maximum number of results the server will return.
						ajaxOptions : {}
					};
					var ajaxSettings = {
						url: null,
						type: 'GET',
						cache: false,
						dataType: 'html',//if 'xml', 'json' or 'jsonp' then success function needs to be modified
						coreSuccess : function(data, textStatus, jqXHR){
							//replace old list with new
							$this.html(data).find('.alias').hide();
							//remember current menu values
							$menus.each(function(i){
								var $m = $(this);
								$m.data('jListFetcher').lastServed = $m.val();

							});
							var rows = setRegime.call($this, 'normal');
							ajaxSettings.postSuccess(data, textStatus, jqXHR, $this);
						},
						success: function(){},
						coreError: function(jqXHR, textStatus, errorThrown){
							setRegime.call($this, 'error');
							ajaxSettings.postError(jqXHR, textStatus, errorThrown);
						},
						error: function(){},
						complete: function(jqXHR, textStatus){
						}
					};
					if(options) { $.extend(true, settings, options); }
					$.extend(ajaxSettings, settings.ajaxOptions);
					//Double-shuffles to protect 'coreSuccess' and 'coreError' functions from being overwritten by their equivalent options.
					ajaxSettings.postSuccess = ajaxSettings.success;
					ajaxSettings.success = ajaxSettings.coreSuccess;
					ajaxSettings.postError = ajaxSettings.error;
					ajaxSettings.error = ajaxSettings.coreError;

					var $menus = settings.$searchForm.find('.searchFactor');
					$menus.each(function(i){
						var $m = $(this);
						$m.data('jListFetcher', {
							initVal: $m.val(),
							lastServed: null
						});
					});

					$this.data('jListFetcher', {
						target : $this,
						settings: settings,
						ajaxSettings: ajaxSettings,
						$menus: $menus
					});

					setRegime.call($this, 'normal');
					settings.$searchForm.submit(function(e){
						setList.call($this, e);
						return false;//suppress conventional form submission
					});
				}
			});
		},
		reset: function(){
			//"this" is a jquery object on which this plugin has been invoked.
			return this.each(function(index){
				//"$this" is a tbody object on which this plugin has been invoked.
				var $this = $(this);
				$this.empty();
				setRegime.call($this, 'normal');
				$this.data('jListFetcher').$menus.each(function(){
					var $this = $(this);
					$this.val($this.data('jListFetcher').initVal);
					$this.data('jListFetcher').lastServed = null;
				});
			});
		}
	};
	// ***** Fin: Public Methods *****
	// *******************************

	// *****************************
	// ***** Start: Supervisor *****
	$.fn.jListFetcher = function( method ) {
		if ( methods[method] ) {
			return methods[method].apply( this, Array.prototype.slice.call( arguments, 1 ));
		} else if ( typeof method === 'object' || !method ) {
			return methods.init.apply( this, arguments );
		} else {
			$.error( 'Method ' +  method + ' does not exist in jQuery.jListFetcher' );
		}
	};
	// ***** Fin: Supervisor *****
	// ***************************
})( jQuery );